﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace EasyClass {
	public class MyNetwork {
		private const int broadcastPort = 3701;  //server's broadcast port
		private const int receivePort = 3702;  //server's public receiving port, only used to receive "detect" message
		private int curClientPort = 3710;  //private port between client and server, client distinctly
		private Dictionary<string, int> clients;  //ip address to port
		private Controller controller;
        private string ip;

		public MyNetwork(Controller c) {
			controller = c;
			controller.SetMyNetwork(this);

			clients = new Dictionary<string, int>();
			Thread detect = new Thread(DetectNewClient);
			detect.Start();
			Thread receive = new Thread(ReceiveNewClient);
			receive.Start();
		}

		private void SendData(IPAddress ipAddress, int port, string content) {  //basic send function
			MyConsole.WriteLine("sending " + ipAddress.ToString() + " " + port + " \"" + content + "\"");
			Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
			sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
			IPEndPoint iep = new IPEndPoint(ipAddress, port);
			sock.SendTo(Encoding.ASCII.GetBytes(content), iep);
			sock.Close();
		}

		private string ReceiveData(IPAddress ipAddress, int port) {  //basic receive function
            MyConsole.WriteLine("ReceiveData called, ipAddress=" + ipAddress + " port=" + port);
			Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            IPEndPoint iep;
            iep = new IPEndPoint(ipAddress, port);
            if (ipAddress.Equals(IPAddress.Parse("0.0.0.0")))
                iep = new IPEndPoint(ipAddress, port);
            else
                iep = new IPEndPoint(IPAddress.Parse(ip), port);
            sock.Bind(iep);
            EndPoint ep = (EndPoint)iep;
			byte[] data = new byte[1000];
			sock.ReceiveTimeout = 2147483647;
			int count = sock.ReceiveFrom(data, ref ep);
			sock.Close();
            String result = Encoding.ASCII.GetString(data, 0, count);
            MyConsole.WriteLine("received " + ipAddress.ToString() + " " + port + " \"" + result + "\"");
			return result;
		}

		private void ReceiveNewClient() {  //from public receive port
			while (true) {
				string content = ReceiveData(IPAddress.Any, receivePort);
				if (content.Substring(0, 6) != "detect") continue;  //unknown data
				CreateNewClient(content.Substring(7, content.Length - 7));  //detect+ip
			}
		}

		private void DetectNewClient() {  //broadcast perioadically 
			IPAddress[] addressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;  //get current ip
            int i;
            for (i = 0; i != addressList.Length - 1; ++i)
                if (addressList[i].AddressFamily == AddressFamily.InterNetwork && !addressList[i].Equals(IPAddress.Parse("127.0.0.1")))
                    break;
            ip = addressList[i].ToString();
			while (true) {
				MyConsole.WriteLine("server's ip is " + ip);  //MyConsole
				string content = "detect+" + ip;
				SendData(IPAddress.Broadcast, broadcastPort, content);
				Thread.Sleep(5000);  //detect every 5 seconds
			}
		}

		private void ListenClient(object ip_port) {  //from client-specific port, send to controller
			string s = ip_port.ToString();
			MyConsole.WriteLine("listening client " + s);
			int k = s.IndexOf(':');
			IPAddress ipAddress = IPAddress.Parse(s.Substring(0, k));
			int port = Int32.Parse(s.Substring(k + 1, s.Length-k-1));
			while (true) {
				//string content = ReceiveData(ipAddress, port);
                string content = ReceiveData(IPAddress.Parse(ip), port);
				//report to controller
				MyConsole.WriteLine("received: " + content + " from: " + s);  //MyConsole
				controller.HandleReceivedData(ipAddress, content);
			}
		}
		
		private void CreateNewClient(string ip) {  //assign port number to client and start listening
			if (!clients.ContainsKey(ip)) { //client already exists
                Thread listen = new Thread(new ParameterizedThreadStart(ListenClient));  //listen to client
                clients[ip] = curClientPort++;  //assign new port
                listen.Start(ip + ":" + clients[ip].ToString());
                MyConsole.WriteLine("new client found, ip is " + ip);  //MyConsole
                SendData(IPAddress.Parse(ip), broadcastPort, "assign+" + clients[ip].ToString());  //tell the client its port
			} else
                SendData(IPAddress.Parse(ip), broadcastPort, "assign+" + clients[ip].ToString());  //tell the client its port
		}

		public void SendClient(IPAddress ipAddress, string content) {
			SendData(ipAddress, broadcastPort, content);
		}
	}
}
